//OK: convert from  std::io::Error, 
//OK: use the From/Into Trait
//OK: self error_kind
//OK: use thiserror
//OK: used in Result, 

use std::fmt;


#[derive(Debug)]
enum Repr<K, M> 
where K: fmt::Display,
    M: fmt::Display
{
    App(K, M),
    Io(std::io::Error),
}

impl<K, M>fmt::Display for Repr<K, M> 
where K: fmt::Display,
    M: fmt::Display
{
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Repr::App(kind, message) => {
                write!(fmt, "{kind}:{message}")
            },
            Repr::Io(e) => e.fmt(fmt),
        }
    }
}


#[derive(Debug)]
enum AppErrorKind {
    OK,
    InvalidLines,
}
impl fmt::Display for AppErrorKind {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>)-> fmt::Result {
        match &self {
            AppErrorKind::OK => write!(fmt, "OK(0)"),
            AppErrorKind::InvalidLines => write!(fmt, "InvalidLines(1)"),
        }
    }
}




#[derive(Debug)]
struct Error {
    repr: Repr<AppErrorKind, String>,
}
impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.repr {
           Repr::App(..) => None,
           Repr::Io(e) => Some(e),
        }
    }
}
impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.repr.fmt(fmt)
    }
}

use std::convert::From;
impl From<std::io::Error> for Error {
    fn from(value: std::io::Error) -> Self {
        Error{ 
            repr: Repr::Io(value),
        }
    }
}

impl From<(AppErrorKind, &str)> for Error {
    fn from(value: (AppErrorKind, &str)) -> Self {
        Self { repr: Repr::App(value.0, value.1.to_owned())}
    }
}

#[cfg(test)]
mod test {
    /*
     * Error :  
     *  Repr{ 
     *      Os(i32), 
     *      Simple(ErrorKind), 
     *      SimpleMessage(ErrorKind, &'static &'static str), 
     *      Custom(Box<Custom>)
     *          => Custom{ErrorKind, error: Box<dyn error::Error+Send+Sync> }
     *  }
     * Error_create: 
     *  1. Error::last_os_error() or  Error::from_raw_os_error(code)
     *      -> Call Repr::Os(code)
     *  2. Error::from(kind) :  from ErrorKind cvt to io::Error 
     *      -> call Repr::Simple(kind)
     *  3. Error::new_const(kind, message); 
     *      -> call Repr::SimpleMessage(kind, message)
     *  4. Error::other(custom_error); 
     *      -> call create_Error with Repre::Custom(Box::new(Custom{kind, error}));
     *  5. Error::new(kind, E) where E: Into<Box<dyn error::Error + Send + Sync>>
     *
     */
    #[test]
    fn use_io_error(){
        use std::io::{Error, ErrorKind};
        let e = Error::from_raw_os_error(22);
        assert_eq!(e.kind(), ErrorKind::InvalidInput);

        let e = Error::from(ErrorKind::InvalidData);
        assert_eq!(ErrorKind::InvalidData, e.kind());

        let e: Error = ErrorKind::InvalidData.into();
        assert_eq!(e.kind(), ErrorKind::InvalidData);

        let e = Error::new(ErrorKind::AddrInUse, "addr in use ahaha");
        assert_eq!(e.kind(), ErrorKind::AddrInUse);

        let e = Error::other("hello world");
        println!("{e}");
    }


    
    #[test]
    fn use_from_trait_into_trait(){
        use std::io::{Error, ErrorKind};
        // impl From<ErrorKind> to Error 
        // one-way convert: 
        //
        // => From ErrorKind to Self(Error)
        let _e = Error::from(ErrorKind::InvalidData);

        // => ErrorKind into Error
        let _e:Error = ErrorKind::InvalidData.into();
        // => also write as this:
        let e  = Into::<Error>::into(ErrorKind::InvalidData);
        println!("{e:?}");

        /*
        impl<T, U> Into<U> for T
        where
            U: From<T>,
        {
            /// Calls `U::from(self)`.
            ///
            /// That is, this conversion is whatever the implementation of
            /// <code>[From]&lt;T&gt; for U</code> chooses to do.
            #[inline]
            #[track_caller]
            fn into(self) -> U {
                U::from(self)
            }
        }
        */
    }

    use super::*;
    use std::io;
    #[test]
    fn use_my_error(){
        let ioe = io::Error::from_raw_os_error(22);
        println!("io::Error: {ioe}, kind:{}", ioe.kind());


        let e = super::Error::from(ioe);
        println!("my error: {e}");
        let e = super::Error::from(io::Error::from_raw_os_error(23));
        println!("my error: {e}");

        let e: io::Error = io::Error::other(e);
        println!("io::Error: {}", e.into_inner().unwrap());

        let e = super::Error::from((AppErrorKind::OK, "OK hahaha"));
        println!("my error: {}", e);
        let e: Error = (AppErrorKind::InvalidLines, "invalid lines of 100").into();
        println!("my error: {}", e);
    }

    use thiserror::Error;

    #[derive(Error, Debug)]
    enum MyError {
        #[error("OK")]
        OK,
        #[error("Invalid Argument")]
        InvalidArgument,
        #[error("invalid file name:{0}")]
        InvalidFileName(String),
        #[error("invalid info, fname:{fname}")]
        InvalidInfo{fname:String, fd:u64},
    }

    use anyhow::Result;
    fn get_anyhow_error() -> Result<u32> {
        Err(MyError::InvalidArgument.into())
    }
    
    #[test]
    fn use_this_error() {
        let e = MyError::OK;
        println!("{e}");
        let e = MyError::InvalidArgument;
        println!("{e}");
        
        let e = MyError::InvalidFileName("text_file.txt".to_string());
        println!("{e}");

        let e = MyError::InvalidInfo{fname:"text_file.txt".to_string(), fd:10};
        println!("{e}");
        println!("{}", get_anyhow_error().err().unwrap());
    }
}
